from general_utilities import *
from ready_dungeon import *
from highscores import *
from object_list import *
from math import sqrt, atan
import random

MAX_MAP_DIM = 23 
MAP_WIDTH = 23 # must be odd
MAP_HEIGHT = 23 # must be odd

class Pos:
  level = 1    # level closest to exit (may be up or down)   
  r_down = 0   # room on top left of level
  r_right = 0 
  down=0       # tile on top left of room
  right=0
  
  def __str__(self):
    return "( " + str(self.level) + " " + str(self.r_down) + " " + str(self.r_right) + " " + str(self.down) + " " + str(self.right) + ")"
  
  def copy(self):
    return Pos(self.level, self.r_down, self.r_right, self.down, self.right)
  
  def __init__(self,level,r_down,r_right,down,right):
    self.level = level
    self.r_down = r_down
    self.r_right = r_right
    self.down = down
    self.right = right
    
  def __eq__(self,other):
    if self.level <> other.level:
      return 0
    if self.r_down <> other.r_down or self.r_right <> other.r_right:
      return 0
    if self.down <> other.down or self.right <> other.right:
      return 0
    return 1

  def __ne__(self,other):
    if self.level <> other.level:
      return 1
    if self.r_down <> other.r_down or self.r_right <> other.r_right:
      return 1
    if self.down <> other.down or self.right <> other.right:
      return 1
    return 0
  
  def same_level(self,other):
    return self.level==other.level

  def same_room(self,other):
    if self.level<>other.level:
      return 0
    if self.r_down<>other.r_down:
      return 0
    if self.r_right<>other.r_right:
      return 0
    return 1
    
  def to_tuple(self):
    return (self.level,self.r_down,self.r_right,self.down,self.right)
  
  def max_orthogonal_distance_within_room(self,other):
    if not self.same_room(other):
      return None
    return max(abs(self.down-other.down),abs(self.right-other.right))
  
  def exact_step_direction(self,other):
    # return a DIR_ code or None
    if not self.same_room(other):
      return None
    if self.down==other.down:
      if self.right>other.right:
        return DIR_LEFT
      elif self.right<other.right:
        return DIR_RIGHT
      elif self.right==other.right:
        return None
    elif self.right==other.right:
      if self.down>other.down:
        return DIR_UP
      elif self.down<other.down:
        return DIR_DOWN
    elif abs(self.right-other.right)==abs(self.down-other.down):
      if self.down>other.down:
        if self.right>other.right:
          return DIR_NW
        elif self.right<other.right:
          return DIR_NE
      elif self.down<other.down:
        if self.right>other.right:
          return DIR_SW
        elif self.right<other.right:
          return DIR_SE        
    else:
      return None

  def distance_within_room(self,other):
    if not self.same_room(other):
      return None
    return sqrt(pow(self.down-other.down,2)+pow(self.right-other.right,2))

  def direction_towards(self,other):
    # return a DIR code
    if not self.same_room(other):
      return None
    if self==other:
      return DIR_HERE
    down = other.down - self.down
    right = other.right - self.right
    if right==0:
      if down>0:
        return DIR_DOWN
      elif down<0:
        return DIR_UP
    angle = atan(float(down)/float(right))*360/(2*3.14159265)
    if right<0:
      angle = angle+180
    if angle<0:
      angle = angle+360
    if angle>337.5 or angle<=22.5:
      return DIR_RIGHT
    elif angle<=67.5:
      return DIR_SE
    elif angle<=112.5:
      return DIR_DOWN
    elif angle<=157.5:
      return DIR_SW
    elif angle<=202.5:
      return DIR_LEFT
    elif angle<=247.5:
      return DIR_NW
    elif angle<=292.5:
      return DIR_UP
    elif angle<=337.5:
      return DIR_NE

class Object:
  def __init__(self,type):
    self.type = type # an "O_" string
    
  def info(self):
    # returns something descending from a Basic_object_info
    return olib.o[self.type]

class Tile_info:
  def __init__(self,desc="The dungeon floor, paved with flagstones.",potentially_walkable=1,leapable=1,shootable=1,is_portal=0,is_exit=0,is_monster_exit=0,can_contain_objects=1,is_fiery=0,is_watery=0,refreshing=0,can_be_unlocked=0,slow_without_webwalk=0,fall_into=0,key_type=None,no_teleport=1,is_teleport_rune=0,is_confusion_rune=0,is_pain_rune=0,looks_like_floor=0,is_letter=0):
    self.desc = desc
    self.potentially_walkable = potentially_walkable
    self.leapable = leapable
    self.shootable = shootable
    self.is_portal = is_portal
    self.is_exit = is_exit
    self.is_monster_exit = is_monster_exit
    self.can_contain_objects = can_contain_objects
    self.is_fiery = is_fiery
    self.is_watery = is_watery
    self.refreshing = refreshing
    self.can_be_unlocked = can_be_unlocked
    self.key_type = key_type
    self.slow_without_webwalk = slow_without_webwalk
    self.fall_into = fall_into
    self.no_teleport = no_teleport
    self.is_teleport_rune = is_teleport_rune
    self.is_confusion_rune = is_confusion_rune
    self.is_pain_rune = is_pain_rune
    self.looks_like_floor = looks_like_floor
    self.is_letter = is_letter
  
  def description(self,jammed,exit_to):
    if jammed:
      return "You have jammed this lock by trying to pick it. Now you will only be able to open it if you carry the key."
    if exit_to:
      if exit_to[0]==D_OUTSIDE:
        return "This exit leads outside. If you pass through, you will escape the dungeon. You will not have succeeded in your quest but at least you will have survived."
      if exit_to[0]<>D_MAIN_DUNGEON:
        return dlib.d[exit_to[0]].description
    return self.desc
    
  def walkable(self,entity):
    if self.can_be_unlocked:
      if entity.is_character:
        if self.key_type is None or self.is_key_to(entity.keys[0]) or self.is_key_to(entity.keys[1]) or self.is_key_to(entity.keys[2]) or self.is_key_to(entity.keys[3]) or self.is_key_to(entity.keys[4]):
          return 1
      return 0
    if not self.potentially_walkable:
      return 0
    if not self.is_fiery and entity.info().moves_through_flame_only:
      return 0
    if not self.is_watery:
      if entity.info().moves_through_water_only:
        return 0
    else:
      if not entity.info().can_swim:
        return 0
    return 1

  def is_key_to(self,object):  
    if object is None or self.key_type is None:
      return 0
    if object.type==self.key_type:
      return 1
    return 0
  
  def walkable_test_for_pathfinding(self,water_ok):
    if self.can_be_unlocked or not self.potentially_walkable:
      return 0
    if self.is_watery and not water_ok:
      return 0
    return 1

class Wall_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,potentially_walkable=0,leapable=0,shootable=0,can_contain_objects=0,**parms)

class Water_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,is_watery=1,can_contain_objects=0,**parms)

class Passable_furniture_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,can_contain_objects=0,**parms)

class Pit_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,fall_into=1,can_contain_objects=0,**parms)

class Rune_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,looks_like_floor=1,can_contain_objects=0,**parms)

class Teleport_rune_tile_info(Rune_tile_info):
  def __init__(self,desc,**parms):
    Rune_tile_info.__init__(self,desc=desc,is_teleport_rune=1,**parms)
    
class Confusion_rune_tile_info(Rune_tile_info):
  def __init__(self,desc,**parms):
    Rune_tile_info.__init__(self,desc=desc,is_confusion_rune=1,**parms)
    
class Pain_rune_tile_info(Rune_tile_info):
  def __init__(self,desc,**parms):
    Rune_tile_info.__init__(self,desc=desc,is_pain_rune=1,**parms)    

class Fire_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,is_fiery=1,can_contain_objects=0,**parms)

class Locked_gate_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,leapable=0,shootable=0,potentially_walkable=0,can_contain_objects=0,can_be_unlocked=1,**parms)

class Portal_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,is_portal=1,leapable=0,can_contain_objects=0,**parms)

class Locked_door_tile_info(Portal_tile_info):
  def __init__(self,desc,**parms):
    Portal_tile_info.__init__(self,desc=desc,can_be_unlocked=1,**parms)

class Exit_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,is_exit=1,is_portal=1,can_contain_objects=0,leapable=0,**parms)
    
class Hole_tile_info(Tile_info):
  def __init__(self,desc,**parms):
    Tile_info.__init__(self,desc=desc,is_monster_exit=1,can_contain_objects=0,leapable=0,**parms)
  
class Tile_library:
  def __init__(self):
    self.t = {} # key is T_ code, value is derived from Tile_info
    self.t[T_FLOOR] = Tile_info(no_teleport=0)
    self.t[T_FLOOR_NOTELEPORT] = Tile_info() # no_teleport=1 is the default
    self.t[T_WEB] = Tile_info(slow_without_webwalk=1,desc="A mass of clinging spiderwebs.")
    self.t[T_LETTER] = Wall_tile_info(desc="A solid stone wall.",is_letter=1)
    self.t[T_WALL] = Wall_tile_info(desc="A solid stone wall.")
    self.t[T_WATER_WALL] = Wall_tile_info(desc="Deep water. Pity you can't swim!")
    self.t[T_WATER] = Water_tile_info(desc="Deep water. Pity you can't swim!")
    self.t[T_SIGNPOST] = Passable_furniture_tile_info(desc="A signpost stands here...")
    self.t[T_GRAVEMARKER] = Passable_furniture_tile_info(desc="This gravestone seems to have been put up quite recently. There is something inscribed on it - perhaps if you go closer you will be able to read the epitaph.")
    self.t[T_FOUNTAIN] = Passable_furniture_tile_info(refreshing=1,desc="A sparkling fountain ceaselessly sprays water, creating a pleasant tinkling sound. You feel that you will be healed and refreshed by drinking the clear water.")
    self.t[T_STATUE_WALL] = Wall_tile_info(desc="A statue carved in olden times stands here.")
    self.t[T_BOOKCASE] = Wall_tile_info(desc="A bookcase heaped with dusty tomes.")
    self.t[T_DESK] = Wall_tile_info(desc="A desk covered with books, parchments, and quill pens.")
    self.t[T_RUBBLE] = Wall_tile_info(desc="A heap of stones and rubble.")
    self.t[T_DARK_ALTAR] = Wall_tile_info(desc="A sinister altar to the dark gods!" )
    self.t[T_TABLE] = Wall_tile_info(desc="A crudely made wooden table.")
    self.t[T_STATUE] = Wall_tile_info(desc="A tall statue stands here, depicting a warrior from ancient times. Its face is carved into a forbidding scowl.")
    self.t[T_THRONE] = Passable_furniture_tile_info(desc="A magnificent throne is here!")
    self.t[T_STOOL] = Passable_furniture_tile_info(desc="A small wooden stool with three crooked legs.")
    self.t[T_GRAVESTONE] = Passable_furniture_tile_info(desc="An ancient gravestone. The epitaph is written in characters you do not recognise.")
    self.t[T_GRAVEYARD] = Passable_furniture_tile_info(desc="Ancient, mossy grave markers.")
    self.t[T_PIT] = Pit_tile_info(desc="A pit in the floor! Take care not to fall in...")
    self.t[T_RUNE_TELEPORT] = Teleport_rune_tile_info(desc="A magical symbol is inscribed on the floor here.")
    self.t[T_RUNE_CONFUSION] = Confusion_rune_tile_info(desc="A magical symbol is inscribed on the floor here.")
    self.t[T_RUNE_PAIN] = Pain_rune_tile_info(desc="A magical symbol is inscribed on the floor here.")
    self.t[T_FIRE_PIT] = Fire_tile_info(desc="A burning pit!")
    self.t[T_CAMPFIRE] = Fire_tile_info(desc="A small, smoky fire.")
    self.t[T_DOOR] = Portal_tile_info(desc="This door leads to the next room.")
    self.t[T_STAIRS_UP] = Portal_tile_info(desc="These stairs lead upwards to the next level.")
    self.t[T_STAIRS_DOWN] = Portal_tile_info(desc="These stairs lead downwards to the next level.")
    self.t[T_LOCKED_DOOR_GOLD] = Locked_door_tile_info(key_type=O_KEY_GOLD,desc="This door is securely locked - you will only be able to open it if you carry the key. The lock cannot be picked.")
    self.t[T_LOCKED_DOOR_RED] = Locked_door_tile_info(key_type=O_KEY_RED,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_LOCKED_DOOR_GREEN] = Locked_door_tile_info(key_type=O_KEY_GREEN,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_LOCKED_DOOR_BLUE] = Locked_door_tile_info(key_type=O_KEY_BLUE,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_LOCKED_DOOR_SILVER] = Locked_door_tile_info(key_type=O_KEY_SILVER,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_EXIT] = Exit_tile_info(desc="This door exits the dungeon you are in.")
    self.t[T_ENTRANCE] = Passable_furniture_tile_info(desc="This is where you came in.")
    self.t[T_HOLE] = Hole_tile_info(desc="This hole in the wall opens into a dark twisty passage, and there could be monsters lurking beyond. You are hesitant to go inside.")
    self.t[T_LOCKED_GATE_GOLD] = Locked_gate_tile_info(key_type=O_KEY_GOLD,desc="This door is securely locked - you will only be able to open it if you carry the key. The lock cannot be picked.")
    self.t[T_LOCKED_GATE_RED] = Locked_gate_tile_info(key_type=O_KEY_RED,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_LOCKED_GATE_GREEN] = Locked_gate_tile_info(key_type=O_KEY_GREEN,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_LOCKED_GATE_BLUE] = Locked_gate_tile_info(key_type=O_KEY_BLUE,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_LOCKED_GATE_SILVER] = Locked_gate_tile_info(key_type=O_KEY_SILVER,desc="This door is locked - you will only be able to open it if you carry the key (or if you know how to pick locks...)")
    self.t[T_GATE] = Locked_gate_tile_info(key_type=None,desc="This door is unlocked. You can walk through it any time you want. The monsters won't open it, though.")

tlib = Tile_library()

class Tile:
  def __init__(self,type,wall_type,floor_type,object_type=None,message=None,exit_to=None):
    self.type = type # the tile name, a T_ constant
    if object_type is None:
      self.object = None
    else:
      self.object = Object(object_type)
    self.wall_type = wall_type # the wall look, a W_ constant
    self.floor_type = floor_type # the floor look, a F_ constant
    self.message = message # set for signposts and gravemarkers
    self.exit_to = exit_to # set for exits. 2-tuple, with first element a D_ code and second ignored - unless first element is D_MAIN_DUNGEON, in which case second is a difficulty level
    # if exit_to is not None:
    #   print exit_to
    self.jammed = 0
    self.to_pos = None # will be set by a higher level function
    self.revealed = 0

  def info(self):
    return tlib.t[self.type]
  
class Room:  
  def __init__(self,ready_room):
    self.level = ready_room.level
    self.r_down = ready_room.r_down
    self.r_right = ready_room.r_right
    self.width = ready_room.width
    self.height = ready_room.height
    self.ambient_messages = ready_room.ambient_messages # [Possible_message]
    if self.width > MAX_MAP_DIM or self.height > MAX_MAP_DIM:
      raise SystemExit
    self.door_up = 0
    self.door_down = 0
    self.door_right = 0
    self.door_left = 0
    self.doors_pos = []
    self.exit_pos = None
    self.monster_exit_pos = None
    self.stairs_up_pos = None
    self.stairs_down_pos = None
    self.ever_visited = 0
    self.fiery_tiles = [] # [Pos]
    self.tiles = [] # 2-array of Tile
    self.has_fountain = 0
    for i in range(self.height):
      row = []
      for j in range(self.width):
        if i==0 or j==0 or j==self.width-1 or i==self.height-1:
          on_wall = 1
        else:
          on_wall = 0
        this_tile = self.make_tile(ready_room.get_tile_info(i,j),on_wall)
        if this_tile is None:
          print "about to crash"
          print ready_room.get_tile_info(i,j).type_abbrev
          print on_wall
          print '\n'
          print ready_room.grid
        tile_info = this_tile.info()
        row.append(this_tile)
        this_pos = Pos(self.level,self.r_down,self.r_right,i,j)
        if tile_info.is_fiery:
          self.fiery_tiles.append(this_pos)
        elif tile_info.refreshing:
          self.has_fountain = 1
        elif tile_info.is_exit:
          self.exit_pos = this_pos
        elif tile_info.is_monster_exit:
          self.monster_exit_pos = this_pos
        elif tile_info.is_portal:
          # to_pos will be set later for stairs, or now for doors
          if this_tile.type==T_STAIRS_UP:
            self.stairs_up_pos = this_pos
          elif this_tile.type==T_STAIRS_DOWN:
            self.stairs_down_pos = this_pos
          else:
            self.doors_pos.append(this_pos)
            if i==0: # top of room
              this_tile.to_pos = Pos(self.level,self.r_down-1,self.r_right,22,11)
              self.door_up = 1
            elif i==self.height-1: # bottom of room
              this_tile.to_pos = Pos(self.level,self.r_down+1,self.r_right,0,11)
              self.door_down = 1
            elif j==0: # left of room
              this_tile.to_pos = Pos(self.level,self.r_down,self.r_right-1,11,22)
              self.door_left = 1
            elif j==self.width-1: # right of room
              this_tile.to_pos = Pos(self.level,self.r_down,self.r_right+1,11,0)
              self.door_right = 1
      self.tiles.append(row)

  def make_tile(self,ready_tile_info,on_wall):
    type_abbrev = ready_tile_info.type_abbrev
    floor_type = ready_tile_info.floor_type
    wall_type = ready_tile_info.wall_type
    object_type = ready_tile_info.object_type
    message = ready_tile_info.message
    exit_to = ready_tile_info.exit_to
    if on_wall:
      if type_abbrev=="+":
        return Tile(T_DOOR,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="~":
        return Tile(T_WATER_WALL,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="#":
        return Tile(T_WALL,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="&":
        print "putting a statue-wall on the outside"
        return Tile(T_STATUE_WALL,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="{":
        return Tile(T_ENTRANCE,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="}":
        return Tile(T_EXIT,wall_type,floor_type,object_type=object_type,exit_to=exit_to)
      elif type_abbrev=="$":
        return Tile(T_LOCKED_DOOR_GOLD,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="S":
        return Tile(T_LOCKED_DOOR_SILVER,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="R":
        return Tile(T_LOCKED_DOOR_RED,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="G":
        return Tile(T_LOCKED_DOOR_GREEN,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="B":
        return Tile(T_LOCKED_DOOR_BLUE,wall_type,floor_type,object_type=object_type)
      # or 'D' is a random door
      elif type_abbrev=="H":
        return Tile(T_HOLE,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="<":
        return Tile(T_STAIRS_UP,wall_type,floor_type,object_type=object_type)
      elif type_abbrev==">":
        return Tile(T_STAIRS_DOWN,wall_type,floor_type,object_type=object_type)
    else:      
      if type_abbrev==".":
        return Tile(T_FLOOR,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="#":
        return Tile(T_WALL,wall_type,floor_type,object_type=object_type)
      elif type_abbrev>='A' and type_abbrev<='Z':
        return Tile(T_LETTER,wall_type,floor_type,object_type=object_type,message=type_abbrev)
      elif type_abbrev=="~":
        return Tile(T_WATER,wall_type,floor_type,object_type=object_type)        
      elif type_abbrev=="*":
        return Tile(T_FIRE_PIT,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="\\":
        return Tile(T_LOCKED_GATE_GOLD,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="`":
        return Tile(T_LOCKED_GATE_SILVER,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="-":
        return Tile(T_LOCKED_GATE_GREEN,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="=":
        return Tile(T_LOCKED_GATE_RED,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="_":
        return Tile(T_LOCKED_GATE_BLUE,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="[":
        return Tile(T_GATE,wall_type,floor_type,object_type=object_type)
      # or '|' is a random gate
      elif type_abbrev=="1":
        return Tile(T_BOOKCASE,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="2":
        return Tile(T_DESK,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="3":
        return Tile(T_DARK_ALTAR,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="4":
        return Tile(T_TABLE,wall_type,floor_type,object_type=object_type)      
      elif type_abbrev=="5":
        return Tile(T_THRONE,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="6":
        return Tile(T_STOOL,wall_type,floor_type,object_type=object_type) 
      elif type_abbrev=="7":
        return Tile(T_GRAVESTONE,wall_type,floor_type,object_type=object_type) 
      elif type_abbrev=="8":
        return Tile(T_STATUE,wall_type,floor_type,object_type=object_type) 
      elif type_abbrev=="9":
        return Tile(T_GRAVEYARD,wall_type,floor_type,object_type=object_type) 
      elif type_abbrev=="0":
        return Tile(T_GRAVEMARKER,wall_type,floor_type,object_type=object_type,message=get_epitaph()) 
      elif type_abbrev==":":
        return Tile(T_RUBBLE,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="!":
        return Tile(T_FOUNTAIN,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="&":
        return Tile(T_STATUE_WALL,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="^":
        return Tile(T_PIT,wall_type,floor_type,object_type=object_type)   
      elif type_abbrev=="?":
        return Tile(T_RUNE_TELEPORT,wall_type,floor_type,object_type=object_type)   
      elif type_abbrev=="(":
        return Tile(T_RUNE_CONFUSION,wall_type,floor_type,object_type=object_type)   
      elif type_abbrev==")":
        return Tile(T_RUNE_PAIN,wall_type,floor_type,object_type=object_type)   
      elif type_abbrev=="/":
        return Tile(T_WEB,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="%":
        return Tile(T_CAMPFIRE,wall_type,floor_type,object_type=object_type)
      elif type_abbrev=="@":
        return Tile(T_SIGNPOST,wall_type,floor_type,object_type=object_type,message=message)
      elif type_abbrev==",":
        return Tile(T_FLOOR_NOTELEPORT,wall_type,floor_type,object_type=object_type)
    print "tile creation failure"
    print "type_abbrev (bracketed by underscores) is _" + type_abbrev + "_"
    print wall_type
    print floor_type
    print object_type
    raise SystemError
             
  def tile_at(self,pos):
    # assumes pos is, in fact, in the room!
    return self.tiles[pos.down][pos.right]
    
  def get_mid_wall(self,dir):
    pos = Pos(self.level,self.r_down,self.r_right,0,0)
    if dir==DIR_RIGHT:
      pos.right = self.width-1
      pos.down = self.height/2
    elif dir==DIR_LEFT:
      pos.right = 0
      pos.down = self.height/2
    elif dir==DIR_UP:
      pos.right = self.width/2
      pos.down = 0
    elif dir==DIR_DOWN:
      pos.right = self.width/2
      pos.down = self.height-1
    return pos

def get_pos_in_dir(d,pos,dir):
  # return None if there is no valid pos there
  new_pos = pos.copy()
  new_pos.down = new_pos.down + DIR_CHANGE_DOWN[dir]
  new_pos.right = new_pos.right + DIR_CHANGE_RIGHT[dir]
  if d.l.valid(new_pos):
    return new_pos
  elif d.l.tile_at(pos).info().is_portal:
    if d.l.tile_at(pos).to_pos is None:
      print pos # an error is about to result
      print d.l.tile_at(pos).type
    return d.l.tile_at(pos).to_pos.copy()
  else:
    return None
  
class Level:
  def __init__(self,ready_level):
    self.level = ready_level.level
    self.r_width = ready_level.r_width
    self.r_height = ready_level.r_height
    self.ambient_messages = ready_level.ambient_messages # [Possible_message]
    self.rooms = [] # 2-array of Room
    for i in range(self.r_height):
      row = []
      for j in range(self.r_width):
        row.append(Room(ready_level.rooms[i][j]))
      self.rooms.append(row)
    # Collect transporter locations from rooms
    self.doors_pos = []
    self.stairs_up_pos = []
    self.stairs_down_pos = []
    self.exits_pos = []
    self.monster_exits_pos = []
    for r_i in range(self.r_height):
      for r_j in range(self.r_width):
        room = self.rooms[r_i][r_j]
        if room.stairs_up_pos is not None:
          self.stairs_up_pos.append(room.stairs_up_pos)
        if room.stairs_down_pos is not None:
          self.stairs_down_pos.append(room.stairs_down_pos)
        self.monster_exits_pos = self.monster_exits_pos + [room.monster_exit_pos]
        self.exits_pos = self.exits_pos + [room.exit_pos]
        self.doors_pos = self.doors_pos + room.doors_pos
    # Distribute noises
    has_fountain = 0
    has_fires = 0
    for r_i in range(self.r_height):
      for r_j in range(self.r_width):
        room = self.rooms[r_i][r_j]
        for r_ii in range(r_i-1,r_i+2):
          for r_jj in range(r_j-1,r_j+2):
            if abs(r_ii)+abs(r_jj)<2:
              if r_ii>=0 and r_jj>=0 and r_ii<self.r_height and r_jj<self.r_width:
                if self.rooms[r_ii][r_jj].has_fountain:
                  has_fountain = 1
                if len(self.rooms[r_ii][r_jj].fiery_tiles)>=3:
                  has_fires = 1
        if self.rooms[r_i][r_j].has_fountain:
          self.rooms[r_i][r_j].ambient_messages.append(Possible_message(0.08,"The fountain splashes merrily.","running_water",volume=0.5))          
        elif has_fountain:
          self.rooms[r_i][r_j].ambient_messages.append(Possible_message(0.05,"You hear the sound of water flowing nearby.","running_water",volume=0.3))
        if len(self.rooms[r_i][r_j].fiery_tiles)>=3:
          self.rooms[r_i][r_j].ambient_messages.append(Possible_message(0.08,"The fires burn hot!","118-Fire02",volume=0.5))          
        elif has_fires:
          self.rooms[r_i][r_j].ambient_messages.append(Possible_message(0.05,"You hear the hissing and roaring of flames nearby.",'118-Fire02',volume=0.3))
          
  def room_at(self,pos):
    # assumes pos is, in fact, in the level!
    return self.rooms[pos.r_down][pos.r_right]
    
  def tile_at(self,pos):
    # assumes pos is, in fact, in the level!
    return self.rooms[pos.r_down][pos.r_right].tile_at(pos)  
  
  def through_door(self,pos,dir):
    # assumes that pos is in the level and that it opens to a loc *in the level* *in direction 'dir'*
    result = pos.copy()
    if dir==DIR_RIGHT:
      result.r_right = result.r_right + 1
      result.right = 0
    elif dir==DIR_DOWN:
      result.r_down = result.r_down + 1
      result.down = 0
    elif dir==DIR_LEFT:
      result.r_right = result.r_right - 1
      new_room = self.room_at(result)
      result.right = new_room.width-1
    elif dir==DIR_UP:
      result.r_down = result.r_down - 1
      new_room = self.room_at(result)
      result.down = new_room.height-1
    return result

class Levels:
  def __init__(self,ready_dungeon):
    # Keys not added here as pathfinding not set up yet. 
    # This will be done later.
    self.ready_dungeon = ready_dungeon
    self.n_levels = len(ready_dungeon.levels)
    self.levels = []  # first level is numbered 0
    for ready_level in ready_dungeon.levels:
      self.levels.append(Level(ready_level))
    self.entry_point = Pos(ready_dungeon.start[0],ready_dungeon.start[1],ready_dungeon.start[2],ready_dungeon.start[3],ready_dungeon.start[4])
    self.temporary_fires = [] # we'll maintain a list here
    self.temporary_fire_rooms = [] # of (level,r_down,r_right)
    self.entry_message = ready_dungeon.entry_message 
    self.exit_message = ready_dungeon.exit_message 
    self.ambient_messages = ready_dungeon.ambient_messages # [Possible_message]
    self.known_routes = {} # see pathfinder module
    # Now go through and connect stairs
    if self.n_levels>1:
      stairs_down_locations = {} # key is level number, value is [Pos]
      stairs_up_locations = {} # key is level number, value is [Pos]
      for level in self.levels:
        stairs_down_locations[level.level] = []
        stairs_up_locations[level.level] = []
        for row in level.rooms:
          for room in row:
            if room.stairs_up_pos is not None:
              stairs_up_locations[level.level].append(room.stairs_up_pos)
            if room.stairs_down_pos is not None:
              stairs_down_locations[level.level].append(room.stairs_down_pos)
      for level_no in range(0,self.n_levels-1):
        stairs_up_next_level = stairs_up_locations[level_no+1]
        stairs_down_this_level = stairs_down_locations[level_no]
        if len(stairs_up_next_level)<>len(stairs_down_this_level):
          raise SystemError
        stairs_up_next_level.sort(key=lambda (x): 1000000*x.r_down+10000*x.r_right+100*x.down+x.right)
        stairs_down_this_level.sort(key=lambda (x): 1000000*x.r_down+10000*x.r_right+100*x.down+x.right)
        for i in range(len(stairs_up_next_level)):
          self.tile_at(stairs_up_next_level[i]).to_pos = stairs_down_this_level[i]
          self.tile_at(stairs_down_this_level[i]).to_pos = stairs_up_next_level[i]

  def is_temporary_fire(self,pos):   
    if (pos.level,pos.r_down,pos.r_right) in self.temporary_fire_rooms:
      if pos in self.temporary_fires:
        return 1
    return 0
  
  def remove_temporary_fire(self,pos):
    self.temporary_fires.remove(pos)
    if len(self.temporary_fires)==0:
      self.temporary_fire_rooms = []
      
  def add_temporary_fire(self,pos):
    if not self.is_temporary_fire(pos):
      self.temporary_fires.append(pos.copy())
      if not ((pos.level,pos.r_down,pos.r_right) in self.temporary_fire_rooms):
        self.temporary_fire_rooms.append((pos.level,pos.r_down,pos.r_right))

  def deepest_level(self):
    return self.n_levels-1
        
  def level_at(self,pos):
    return self.levels[pos.level]
 
  def room_at(self,pos):
    # assumes pos is, in fact, in the level!
    return self.levels[pos.level].rooms[pos.r_down][pos.r_right]
    
  def tile_at(self,pos):
    # assumes pos is, in fact, in the level and room!
    return self.levels[pos.level].rooms[pos.r_down][pos.r_right].tile_at(pos)  
  
  def valid(self,pos):
    # is this tile in the map?
    if pos.level<0 or pos.level>self.deepest_level():
      return 0
    level = self.level_at(pos)
    if pos.r_down<0 or pos.r_right<0 or pos.r_down>=level.r_height or pos.r_right>=level.r_width:
      return 0
    room = self.room_at(pos)
    if pos.down<0 or pos.right<0 or pos.down>=room.height or pos.right>=room.width:
      return 0
    return 1
    
def adjacent(d,pos1,pos2):
  # ONLY IF IN THE SAME ROOM - else use adjacent_inc_through_portal
  if not pos1.same_room(pos2):
    return 0   
  if abs(pos1.down-pos2.down)<=1 and abs(pos1.right-pos2.right)<=1:
    return 1
  else:
    return 0
    
def adjacent_inc_through_portal(d,pos1,pos2):
  if not pos1.same_room(pos2):
    tile1 = d.l.tile_at(pos1)
    if tile1.info().is_portal:
      if tile1.to_pos == pos2:
        return 1
    else:
      return 0   
  if abs(pos1.down-pos2.down)<=1 and abs(pos1.right-pos2.right)<=1:
    return 1
  else:
    return 0

# do some psyco bindings 
psyco.bind(adjacent)
psyco.bind(get_pos_in_dir)
psyco.bind(Levels.tile_at)
psyco.bind(Levels.valid)
psyco.bind(Pos.__init__)
psyco.bind(Pos.same_room)
psyco.bind(Pos.to_tuple)
psyco.bind(Room.make_tile)
psyco.bind(Room.tile_at)
psyco.bind(Room.__init__)
psyco.bind(Tile.__init__)
psyco.bind(Tile.info)    
psyco.bind(Object.__init__)
psyco.bind(Object.info)